﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Collections;
using Team6_Algorithms.AidingClasses;

namespace Team6_Algorithms.HighEnd
{
    /// <summary>
    /// K-means algorithm
    /// Returns an 2d- array of pixel points
    /// Written by Alexander Streng
    /// </summary>
    /// 
    public class Kmeans {

        private static ArrayList Means;
        private static object[,] pixelPoints;
        private static Boolean clusteringRunning;
        private static int meanCounter;
        private static int meansComplete;

        /// <summary>
        /// The start of the k-means algorithm.
        /// Basically this algorithm creates 4 'Means' which contain a color
        /// It then checks each pixel in the image and calculates the mean which it is closest to ( eucledian distance )
        /// then it takes all the pixels assigned to that mean, and calculate a new color for the mean.
        /// This takes place until all the colors are static, and the image does not contain more then 4 colors.
        /// </summary>
        /// <param name="_pixelPoints">the array of pixel points on which the algorithm is run</param>
        /// <returns>the new array of pixelpoints with the new colors.</returns>
        public static object[,] start(object[,]_pixelPoints)
        {
            pixelPoints = _pixelPoints;

            clusteringRunning = true;

            Console.WriteLine("Starting with K-Means.. \n");
           
            createMeans();                                                                // create all the means ( with the number given in the GUI.(default is 3)).

             while (clusteringRunning == true)                                        // the condition for how often to run this loop. Should be until the means almost dont change.
            {																					//It doesnt matter if we filled the list earlier on, we'll be re-assigning the pixelPoints to their means anyway.
                assignPoints();                                                             //Assign each pixel in the image to a Mean

                clusteringRunning = recalcMeans();                               // recalculate the means, and set the boolean for the condition of the loop.
            }

             convertColorToMeanColor();

            Console.WriteLine("Done K-means with " + Means.Count + " means \n");

            return pixelPoints;
        }
        
        /// <summary>
        /// Converts the color of the pixel to the color of the mean
        /// </summary>
        private static void convertColorToMeanColor()
        {
            foreach (PixelPoint p in pixelPoints) // First we need to iterate all the means to see which points are assigned.
            {
                if (p.id > 0)
                {
                    p.RedColor = p.mean.Red;
                    p.GreenColor = p.mean.Green;
                    p.BlueColor = p.mean.Blue;
                }
            }
        }

        /// <summary>
        /// Create the 4 means, and give them colors
        /// </summary>
        private static void createMeans()
        {

            Random random = new Random(); //create a new random object for randomizing the means.
			
            Means = new ArrayList();//Allocate a new arraylist for the means.

            Means.Add(new Mean("black", 0,0,0));
            Means.Add(new Mean("yello", 255, 255, 0));
            Means.Add(new Mean("green", 0, 255, 0));
            Means.Add(new Mean("white", 255, 255, 255));
        }

        /// <summary>
        /// Assign each pixel the color of their closest mean.
        /// This is calculated by the eucledian distance.
        /// </summary>
        private static void assignPoints()
        {
            foreach (PixelPoint p in pixelPoints)
            {
                if (p.id > 0)
                {
                    Color color = Color.FromArgb(p.RedColor, p.GreenColor, p.BlueColor);

                    // lets first check the closest mean

                    Mean mean = null;
                    double checkDistance = Int32.MaxValue;

                    foreach (Mean m in Means)
                    {
                        double distance = Math.Sqrt((Math.Pow((color.R - m.Red), 2.0)) + (Math.Pow((color.G - m.Green), 2.0)) + (Math.Pow((color.B - m.Green), 2.0)));

                        m.distToPix = distance;  // calculate the Euclidean distance (to the current pixel), and sets it in the corresponding Mean.

                        if (distance < checkDistance)
                        {
                            checkDistance = distance;
                            mean = m;
                        }
                    }

                    mean.numPixelinClust++;

                    mean.gemRed += color.R;
                    mean.gemGreen += color.G; //Adds the value of each color to the total value of the color in the mean.
                    mean.gemBlue += color.B;    //This value will later be divided by the total number of pixels assigned to the mean.

                    // then add the appropiate pixelpoint

                    p.mean = mean;
                }
            }
        }

        /// <summary>
        /// re-calculate the color of the means
        /// </summary>
        /// <returns></returns>
        private static Boolean recalcMeans()
        {

            if (meanCounter >= 25) {                  //Its not allowed to make more then 25 cycles, so if this occurs, return a false;

                meanCounter = 0;

                Console.WriteLine("Went through more then 25 cycles; stopping.. " + "\n");

                return false;

            } else {

                meansComplete = 0;

           for (int i = 0; i < Means.Count; i++) //Re-calculate the value's of the Means. 
            {
                    int oldRed = ((Mean)Means[i]).Red;
                    int oldGreen = ((Mean)Means[i]).Green;                                 
                    int oldBlue = ((Mean)Means[i]).Blue;   //this will calculate the average value of the collected Color value's.
                    
               if (((Mean)Means[i]).numPixelinClust != 0)  //This is done by dividing the accumulated color value with the number of pixels assigned to the Mean.
                    {                                                        //first it needs to check if the Means average value isnt 0, because if it is, the calculation will fail.
                        ((Mean)Means[i]).Red = (((Mean)Means[i]).gemRed / ((Mean)Means[i]).numPixelinClust);
                        ((Mean)Means[i]).Green = (((Mean)Means[i]).gemGreen / ((Mean)Means[i]).numPixelinClust);
                        ((Mean)Means[i]).Blue = (((Mean)Means[i]).gemBlue / ((Mean)Means[i]).numPixelinClust);
                    }
                if (Math.Abs(oldRed - ((Mean)Means[i]).Red) == 0 && Math.Abs(oldGreen - ((Mean)Means[i]).Green) == 0 && Math.Abs(oldBlue - ((Mean)Means[i]).Blue) == 0) 
                    {
                        meansComplete++; // If the MeanPoint isnt changing anymore, stop the clustering process.
                    }
                }
                if (meansComplete == (Means.Count - 1)) 
                { //If theres only 1 mean moving, stop the process.
                    Console.WriteLine("Stopping because nearly no means are moving anymore. Went trough " + meanCounter + " cycles\n");
                    meanCounter = 0;
                    return false;
                }
                else 
                {
                    meanCounter++;
                    return true;
                }
            }
        }
    }
}
